/*
 * Copyright (c) Kumo Inc. and affiliates.
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <melon/json/bser/bser.h>

#include <melon/string.h>
#include <melon/portability/gtest.h>

using melon::Dynamic;

static const Dynamic roundtrips[] = {
    1,
    std::numeric_limits<int8_t>::max(),
    std::numeric_limits<int16_t>::max(),
    std::numeric_limits<int32_t>::max(),
    std::numeric_limits<int64_t>::max(),
    std::numeric_limits<int8_t>::min(),
    std::numeric_limits<int16_t>::min(),
    std::numeric_limits<int32_t>::min(),
    std::numeric_limits<int64_t>::min(),
    bool(true),
    bool(false),
    nullptr,
    1.5,
    "hello",
    melon::Dynamic::array(1, 2, 3),
    Dynamic::object("key", "value")("otherkey", "otherval"),
};

// Here's a blob from the watchman test suite
const uint8_t template_blob[] =
        "\x00\x01\x03\x28"
        "\x0b\x00\x03\x02\x02\x03\x04\x6e\x61\x6d\x65\x02"
        "\x03\x03\x61\x67\x65\x03\x03\x02\x03\x04\x66\x72"
        "\x65\x64\x03\x14\x02\x03\x04\x70\x65\x74\x65\x03"
        "\x1e\x0c\x03\x19";

// and here's what it represents
static const Dynamic template_dynamic = melon::Dynamic::array(
    Dynamic::object("name", "fred")("age", 20),
    Dynamic::object("name", "pete")("age", 30),
    Dynamic::object("name", nullptr)("age", 25));

TEST(Bser, RoundTrip) {
    Dynamic decoded(nullptr);
    melon::kmstring str;

    for (const auto &dyn: roundtrips) {
        try {
            str = melon::bser::toBser(dyn, melon::bser::serialization_opts());
            decoded = melon::bser::parseBser(str);

            EXPECT_EQ(decoded, dyn);
        } catch (const std::exception &err) {
            KLOG(ERROR) << err.what() << "\nInput: " << dyn.typeName() << ": " << dyn
                    << " decoded back as " << decoded.typeName() << ": " << decoded
                    << "\n"
                    << melon::hexDump(str.data(), str.size());
            throw;
        }
    }
}

TEST(Bser, Template) {
    Dynamic decoded(nullptr);
    melon::kmstring str;
    // Decode the template value provided from elsewhere
    decoded = melon::bser::parseBser(
        melon::ByteRange(template_blob, sizeof(template_blob) - 1));
    EXPECT_EQ(decoded, template_dynamic)
      << "Didn't load template value.\n"
      << "Input: " << template_dynamic.typeName() << ": " << template_dynamic
      << " decoded back as " << decoded.typeName() << ": " << decoded << "\n"
      << melon::hexDump(template_blob, sizeof(template_blob) - 1);

    // Now check that we can generate this same data representation
    melon::bser::serialization_opts opts;
    melon::bser::serialization_opts::TemplateMap templates = {
        std::make_pair(
            &decoded, melon::Dynamic(melon::Dynamic::array("name", "age")))
    };
    opts.templates = templates;

    str = melon::bser::toBser(decoded, opts);
    EXPECT_EQ(
        melon::ByteRange((const uint8_t*)str.data(), str.size()),
        melon::ByteRange(template_blob, sizeof(template_blob) - 1))
      << "Expected:\n"
      << melon::hexDump(template_blob, sizeof(template_blob) - 1) << "\nGot:\n"
      << melon::hexDump(str.data(), str.size());
}

TEST(Bser, PduLength) {
    EXPECT_THROW(
        [] {
        // Try to decode PDU for a short buffer that doesn't even have the
        // complete length available
        auto buf = melon::IOBuf::wrapBuffer(template_blob, 3);
        auto len = melon::bser::decodePduLength(&*buf);
        (void)len;
        KLOG(ERROR) << "managed to return a length, but only had 3 bytes";
        }(),
        std::out_of_range);

    auto buf = melon::IOBuf::wrapBuffer(template_blob, sizeof(template_blob));
    auto len = melon::bser::decodePduLength(&*buf);
    EXPECT_EQ(len, 44) << "PduLength should be 44, got " << len;
}

TEST(Bser, CursorLength) {
    melon::bser::serialization_opts opts;
    std::string inputStr("hello there please break");

    // This test is exercising the decode logic for pathological
    // fragmentation cases.  We try a few permutations with the
    // BSER header being fragmented to tickle boundary conditions

    auto longSerialized = melon::bser::toBser(inputStr, opts);
    for (uint32_t i = 1; i < longSerialized.size(); ++i) {
        melon::IOBufQueue q;

        q.append(melon::IOBuf::wrapBuffer(longSerialized.data(), i));
        uint32_t j = i;
        while (j < longSerialized.size()) {
            q.append(melon::IOBuf::wrapBuffer(&longSerialized[j], 1));
            ++j;
        }

        auto pdu_len = melon::bser::decodePduLength(q.front());
        auto buf = q.split(pdu_len);

        auto hello = melon::bser::parseBser(buf.get());
        EXPECT_EQ(inputStr, hello.asString());
    }
}

/* vim:ts=2:sw=2:et:
 */
